<?php

/**
 * Common基类
 * Date: 2018/11/16
 * Time: 17:20
 */

namespace Lib\Base;

class Common
{
    /**
     * 生成随机字符串
     * @param int $strLength 字符串长度
     * @param int $type 字符串类型 1-数字，2-字符，3-字符数字混合
     * @param int $repeat 字符是否允许重复  0-不重复，1-重复
     */
    public static function getRandStr($strLength = 6, $type = 1, $repeat = 0)
    {
        $rand = '';
        $start = 0;
        $end = 61;
        $orgString = '0123456789QAZXSWEDCVFRTGBNHYUJMKIOLPijnmkopluhbvgytfcxdreszaqw';

        if ($type == 1) {
            $end = 9;
        } else if ($type == 2) {
            $start = 10;
        }

        if ($repeat == 0) {
            if ($type == 1 && $strLength > 10) {
                $repeat = 1;
            }

            if ($type == 2 && $strLength > 52) {
                $repeat = 1;
            }

            if ($type == 3 && $strLength > 62) {
                $repeat = 1;
            }
        }

        for ($i = 0; $i < $strLength; $i++) {
            $iIndex = rand($start, $end);

            $rand .= $orgString[$iIndex];
            if ($repeat == 0) {
                $sTmp = $orgString[$start];
                $orgString[$start] = $orgString[$iIndex];
                $orgString[$iIndex] = $sTmp;
                $start++;
            }
        }
        return $rand;
    }

    public static function time33($str)
    {
        $hash = 5381;
        for ($i = 0; $i < strlen($str); $i++) {
            $hash += (($hash << 5) & 0x7FFFFFF) + ord($str[$i]); //& 0x7FFFFFFF后保证其值为unsigned int范围
        }

        return ($hash & 0x7FFFFFF);
    }

    /**
     * 获取当前账号类型
     * @param void
     * @return string qq|wx
     */
    public static function getAcctype()
    {
        $acctype = Request::param('acctype');
        if ($acctype != '') {
            if (in_array($acctype, array('qq', 'pt', 'qc'))) {
                $acctype = 'qq';
            } else if (in_array($acctype, array('wx', 'weixin'))) {
                $acctype = 'wx';
            }
        } else {
            $openid = Request::param('openid');
            if (ValiDate::checkOpenid($openid, 'qq')) {
                $acctype = 'qq';
            } else if (ValiDate::checkOpenid($openid, 'wx')) {
                $acctype = 'wx';
            }
        }
        return $acctype;
    }

    /**
     *  将cookie数据转成字符串
     * @param array $arrData 需要转字符串的一维数组（key=>value形式），如果不传或者传空数据，默认取$_COOKIE的值
     */
    public static function cookie2Str($arrData = array())
    {
        if (empty($arrData)) {
            $arrData = Request::cookieParam();
        }
        $cookieStr = '';
        if (!empty($arrData)) {
            foreach ($arrData as $key => $value) {
                $cookieStr .= $key . '=' . $value . '; '; //分号和空格不能缺
            }
        }
        if ($cookieStr != '') {
            $cookieStr = substr($cookieStr, 0, -2);
        }
        return $cookieStr;
    }

    /**
     * 对称加密算法加密数据
     * @param array|string $data 需要加密的数据,key=>value组成的一维数组或者字符串
     * @return string|bool 加密成功返回加密字符串，加密失败返回false
     */
    public static function encryptData($data, $secretKey = null)
    {
        if (is_array($data) || is_string($data)) {
            if (is_null($secretKey) && defined('RUN_ENV') && RUN_ENV == 'ulink') {
                $secretKey = Config::getSecretKey();
            }

            if (!$secretKey) {
                return false;
            }
            $dataStr = is_array($data) ? json_encode($data) : $data;
            $encryptStr = rawurlencode(base64_encode(openssl_encrypt($dataStr, 'aes-128-ecb', $secretKey, OPENSSL_RAW_DATA)));
            return $encryptStr;
        }
    }

    /**
     * 对数据进行aes加密
     * @param int|string|array $data 要加密的数据，如果是数组，只能是一维数组，分别对数组中的每个值进行单独加密
     * @return false|string|array 加密失败返回false。如果传入字符串，则返回加密之后的字符串。如果要加密的数据为一个数组，则返回针对每个值进行加密之后的数据，数组下标和原来对应。
     */
    public static function aesEncryptData($data)
    {
        $ivTmp = Config::getFrameworkCfg('userdata.encrypt', 'iv');
        $iv = base64_decode($ivTmp);
        $keyTmp = Config::getFrameworkCfg('userdata.encrypt', 'skey');
        $key = base64_decode($keyTmp);

        if (!$iv || !$key) {
            return false;
        }
        if (is_scalar($data) || self::isOneDimensionalArray($data)) {
            if (is_array($data)) {
                $encryptRet = [];
                foreach ($data as $key1 => $value) {
                    $encryptRet[$key1] = rawurlencode(base64_encode(openssl_encrypt($value, 'aes-256-cbc', $key, 0, $iv)));
                }
            } else {
                $encryptRet = rawurlencode(base64_encode(openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv)));
            }
        } else {
            $encryptRet = false;
        }
        return $encryptRet;
    }

    /**
     * 解密数据
     * @param string $encryptStr 待解密数据
     * @return array|bool 成功返回解密之后数据，key=>value组成的一维数组，失败返回false
     */
    public static function decryptData($encryptStr, $secretKey = null)
    {
        if (is_null($secretKey) && defined('RUN_ENV') && RUN_ENV == 'ulink') {
            $secretKey = Config::getSecretKey();
        }

        if (!$secretKey) {
            return false;
        }

        $encryptStr = rawurldecode($encryptStr);
        $encryptStr = base64_decode($encryptStr);
        $encryptStr = openssl_decrypt($encryptStr, 'aes-128-ecb', $secretKey, OPENSSL_RAW_DATA);
        $ret = json_decode($encryptStr, true);
        return is_null($ret) ? $encryptStr : $ret;
    }

    /**
     * 编码转换
     * @param array|string $inputData 可以是数组和字符串，需要进行编码转换的输入字符串或者数组
     * @param string $inCharset 输入编码
     * @param string $outCharset 需要转换成的编码
     * @return array|string 经过编码转换之后数组或者字符串
     */
    public static function charsetConvert($inputData, $inCharset = 'GBK', $outCharset = 'UTF-8')
    {
        $outputData = $inputData;
        if (is_array($inputData)) {
            $outputData = array();
            if (!empty($inputData)) {
                foreach ($inputData as $key => $value) {
                    $outputData[$key] = self::charsetConvert($value, $inCharset, $outCharset);
                }
            }
        } else if ($inputData) {
            //GBK转utf8先判断一下，如果本身就是utf8就不要转了
            if (strtolower($inCharset) == 'gbk' && strtolower($outCharset) == "utf-8") {
                $outputData = self::GBKtoUTF8($inputData);
            } else {
                $outputData = iconv($inCharset, $outCharset . "//IGNORE", $inputData);
            }
        }
        return $outputData;
    }

    /**
     * GBK转UTF8
     *
     * @param array|string $str 可以是数组和字符串，需要进行编码转换的输入字符串或者数组
     * @return array|string 经过编码转换之后数组或者字符串
     */
    public static function GBKtoUTF8($str)
    {
        if (is_array($str)) {
            foreach ($str as &$value) {
                $value = self::GBKtoUTF8($value);
            }
            return $str;
        } else if (is_string($str) && $str) {
            if (!preg_match('/[\x{4e00}-\x{9fa5}\x{3000}-\x{301e}\x{fe10}-\x{fe19}\x{fe30}-\x{fe44}\x{fe50}-\x{fe6b}\x{ff01}-\x{ffee}]/u', $str)) {
                $str = iconv("GB18030", "UTF-8//IGNORE", $str);
            }
            return $str;
        } else {
            return $str;
        }
    }

    /**
     * UTF8转GBK
     *
     * @param array|string $str 可以是数组和字符串，需要进行编码转换的输入字符串或者数组
     * @return array|string 经过编码转换之后数组或者字符串
     */
    public static function UTF8toGBK($str)
    {
        if (is_array($str)) {
            foreach ($str as &$value) {
                $value = self::UTF8toGBK($value);
            }
            return $str;
        } else if (is_string($str) && $str) {
            if (preg_match('/[\x{4e00}-\x{9fa5}\x{3000}-\x{301e}\x{fe10}-\x{fe19}\x{fe30}-\x{fe44}\x{fe50}-\x{fe6b}\x{ff01}-\x{ffee}]/u', $str)) {
                $str = iconv("UTF-8", "GB18030//IGNORE", $str);
            }
            return $str;
        } else {
            return $str;
        }
    }

    /**
     * url编码解码
     * @param array|string $inputData 可以是数组和字符串，需要进行urldecode的数据
     * @return array|string 经过urldecode转换之后数组或者字符串
     */
    public static function urlDecodeDeep($inputData)
    {
        if (is_array($inputData)) {
            $outputData = array();
            if (!empty($inputData)) {
                foreach ($inputData as $key => $value) {
                    $outputData[$key] = is_array($value) ? self::urlDecodeDeep($value) : rawurldecode($value);
                }
            }
        } else {
            $outputData = rawurldecode($inputData);
        }
        return $outputData;
    }

    /**
     * url编码
     * @param mixed $inputData 可以是数组和字符串，需要进行urlencode的数据
     * @return mixed 经过urlencode转换之后数组或者字符串
     */
    public static function urlEncodeDeep($inputData)
    {
        if (is_array($inputData)) {
            $outputData = array();
            if (!empty($inputData)) {
                foreach ($inputData as $key => $value) {
                    $outputData[$key] = is_array($value) ? self::urlEncodeDeep($value) : rawurlencode($value);
                }
            }
        } else {
            $outputData = rawurlencode($inputData);
        }
        return $outputData;
    }

    /**
     * 反转义字符串
     * @param array|string $inputData 需要反转义的字符串或者数组
     * @return array|string 反转义之后的字符串或者数组
     */
    public static function stripslashesDeep($inputData)
    {
        if ($inputData == '') {
            return '';
        }

        if (get_magic_quotes_gpc()) {
            $inputData = is_array($inputData) ? array_map('self::stripslashesDeep', $inputData) : stripslashes($inputData);
        }
        return $inputData;
    }

    /**
     * 对字符串进行转义，单引号（'）、双引号（"）、反斜线（\）与 NUL（NULL 字符）
     * @param array|string $inputData 需要转义的字符串或者数组
     * @return array|string 转义之后的字符串或者数组
     */
    public static function addSlashesDeep($inputData)
    {
        if (!get_magic_quotes_gpc()) {
            $inputData = is_array($inputData) ? array_map('self::addSlashesDeep', $inputData) : addslashes($inputData);
        }
        return $inputData;
    }

    /**
     * HTML标签转到HTML实体
     * @param array|string $inputData 可以是数组和字符串，需要进行编码转换的输入字符串或者数组
     * @return array|string 经过编码转换之后数组或者字符串
     */
    public static function filterHtml($inputData)
    {
        if (is_array($inputData)) {
            $outputData = array();
            if (!empty($inputData)) {
                foreach ($inputData as $key => $value) {
                    if (is_string($value) || is_array($value)) {
                        $outputData[$key] = is_array($value) ? self::filterHtml($value) : htmlspecialchars($value, ENT_QUOTES);
                    } else {
                        $outputData[$key] = $value;
                    }
                }
            }
        } else {
            if (is_string($inputData)) {
                $outputData = htmlspecialchars($inputData, ENT_QUOTES);
            } else {
                $outputData = $inputData;
            }
        }
        return $outputData;
    }

    /**
     * 去掉字符串首尾指定的字符
     * @param mixed $mixedInput 可以是数组和字符串，需要去掉特殊指定字符的源数据
     * @param mixed $sTrimStr 过滤的字符
     * @return mixed 按照要求处理之后的数据
     */
    public static function trimStr($mixedText, $sTrimStr = " \t\n\r\0\x0B")
    {
        if (is_array($mixedText)) {
            $mixedRet = array();
            if (!empty($mixedText)) {
                foreach ($mixedText as $key => $value) {
                    $mixedRet[$key] = is_array($value) ? self::trimStr($value) : trim($value, $sTrimStr);
                }
            }
        } else {
            $mixedRet = trim($mixedText, $sTrimStr);
        }
        return $mixedRet;
    }

    /**
     * HTML实体转到HTML标签
     * @param mixed $mixedInput 可以是数组和字符串，需要进行编码转换的输入字符串或者数组
     * @param string $sInCharset 输入编码
     * @param string $sOutCharset 需要转换成的编码
     * @return mixed 经过编码转换之后数组或者字符串
     */
    public static function decodeHtml($mixedText)
    {
        if (is_array($mixedText)) {
            $mixedRet = array();
            if (!empty($mixedText)) {
                foreach ($mixedText as $key => $value) {
                    $mixedRet[$key] = is_array($value) ? self::decodeHtml($value) : htmlspecialchars_decode($value, ENT_QUOTES);
                }
            }
        } else {
            $mixedRet = htmlspecialchars_decode($mixedText, ENT_QUOTES);
        }
        return $mixedRet;
    }

    /**
     * 字符串批量替换
     * @param array $aSearch 被替换的字符
     * @param array $aReplace 替换之后的字符
     * @param  array|string $mixedText 需要做替换的字符串或者字符串数组
     * @return array|string 去掉单引号之后的数据
     */
    public static function strReplaceDeep($aSearch, $aReplace, $mixedText)
    {
        if (is_array($mixedText)) {
            $mixedRet = array();
            if (!empty($mixedText)) {
                foreach ($mixedText as $key => $value) {
                    //null 会被替换成空字符串
                    if (is_null($value)) {
                        $mixedRet[$key] = $value;
                    } else {
                        $mixedRet[$key] = is_array($value) ? self::strReplaceDeep($aSearch, $aReplace, $value) : str_replace($aSearch, $aReplace, $value);
                    }
                }
            }
        } else {
            $mixedRet = str_replace($aSearch, $aReplace, $mixedText);
        }
        return $mixedRet;
    }

    /**
     * 生成校验和
     * @param array $data key=>value 组成的一维数组，待生成校验和的数据
     * @param array $salt 生成签名用的盐值，['盐值key'=>'盐值value']
     * @return string|bool 成功返回MD5之后的字符串（大写），失败返回false
     */
    public static function genToken($data, $salt = null)
    {
        if (!self::isOneDimensionalArray($salt) || \count($salt) != 1) {
            recordLog('生成签名的salt不正确！salt=' . var_export($salt, true), __FILE__, __LINE__, LP_ERROR);
            return false;
        }

        if (!self::isOneDimensionalArray($data)) {
            recordLog('待生成签名数据必须为字段名和值组成的一维数组！data=' . var_export($data, true), __FILE__, __LINE__, LP_ERROR);
            return false;
        }
        $encryptData = [];
        foreach ($data as $key => $value) {
            if ($value !== '') {
                $encryptData[$key] = $value;
            }
        }

        if (!empty($encryptData)) {
            ksort($encryptData);
            $encryptData = \array_merge($encryptData, $salt);
            return strtoupper(md5(self::arr2QueryString($encryptData)));
        } else {
            recordLog('带加密的数据不能为空字符串。' . var_export($data, true), __FILE__, __LINE__, LP_ERROR);
            return false;
        }
    }

    /**
     * 数字转成由0-9,a-z,A-Z组成的字符串
     * @param string $number 要转换的数字
     * @param string $string 字符列表
     * @return string 转换之后的字符串
     */
    public static function number2Base62($number, $string = '')
    {
        if (empty($string)) {
            $string = 'vPh7zZwA2LyU4bGq5tcVfIMxJi6XaSoK9CNp0OWljYTHQ8REnmu31BrdgeDkFs+/';
        }

        $out = '';
        for ($t = floor(log10($number) / log10(62)); $t >= 0; $t--) {
            $index = floor($number / pow(62, $t));
            $index = intval($index);
            $out = $out . $string[$index];
            $number = $number - ($index * pow(62, $t));
        }
        return $out;
    }

    /**
     * 获取用户IP地址
     * @return string 用户IP地址
     */
    public static function getClientIp()
    {
        $realip = '';
        if (Request::serverParam()) {
            if (Request::param('sUserIP')) {
                $realip = Request::param('sUserIP');
            } else if (Request::serverParam("HTTP_X_REAL_IP")) {
                $realip = Request::serverParam("HTTP_X_REAL_IP");
            } else if (Request::serverParam("HTTP_X_FORWARDED_FOR")) {
                $realip = Request::serverParam("HTTP_X_FORWARDED_FOR");
            } else if (Request::serverParam("HTTP_CLIENT_IP")) {
                $realip = Request::serverParam("HTTP_CLIENT_IP");
            } else {
                $realip = Request::serverParam("REMOTE_ADDR");
            }
        } else {
            if (getenv("HTTP_X_REAL_IP")) {
                $realip = getenv("HTTP_X_REAL_IP");
            } else if (getenv("HTTP_X_FORWARDED_FOR")) {
                $realip = getenv("HTTP_X_FORWARDED_FOR");
            } else if (getenv("HTTP_CLIENT_IP")) {
                $realip = getenv("HTTP_CLIENT_IP");
            } else {
                $realip = getenv("REMOTE_ADDR");
            }
        }

        if (strpos($realip, ',') === false) {
            $sUserIp = $realip;
        } else {
            $arrUserIp = explode(',', $realip);
            $sUserIp = $arrUserIp[0];
        }
        return $sUserIp;
    }

    /**
     * 判断是否为一维数组
     */
    public static function isOneDimensionalArray($array)
    {
        if (is_array($array) && count($array) == count($array, COUNT_RECURSIVE)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 判断是否为二维数组
     */
    public static function isTwoDimensionalArray($array)
    {
        if (is_array($array)) {
            foreach ($array as $key => $value) {
                if (!self::isOneDimensionalArray($value)) {
                    return false;
                }
            }
        } else {
            return false;
        }
        return true;
    }

    /**
     * 将一维数组转成查询字符串，该函数不会对值进行urlencode操作
     * @param string $data 待转换的数组
     * @return string 查询字符串
     */
    public static function arr2QueryString($data)
    {
        $query = '';
        if (self::isOneDimensionalArray($data)) {
            foreach ($data as $key => $value) {
                $query .= "{$key}={$value}&";
            }
            return \substr($query, 0, -1);
        }
        return $query;
    }

    /**
     *  将查询字符串解析为数组
     */
    public static function queryString2Arr($queryStr)
    {
        $rtnData = [];
        $queryArr = explode('&', $queryStr);
        if (!empty($queryArr)) {
            foreach ($queryArr as $item) {
                $arr = explode('=', $item);
                $rtnData[trim($arr[0])] = trim($arr[1]);
            }
        }
        return $rtnData;
    }

    /**
     * 抽奖算法
     * @param array $packageInfo 礼包和对应的概率信息,二维数组，
     * 每一维必须包含packageId（礼包ID）和pro（中奖概率）2个字符串下标，传入其他的不会删除。
     * 所有礼包的中奖概率之和必须等于100。礼包概率pro直接写数字（支持小数），不用带%。
     * 例如：
     * [
     *      ['packageId' => 123, 'pro' => 0.1],
     *      ['packageId' => 234, 'pro' => 0.9],
     *      ['packageId' => 345, 'pro' => 99]
     * ]
     * @return array|bool 成功返回抽中的礼包数组信息，失败返回false
     */
    public static function getRandPackage(array $packageInfo, int $enlarge = 1000000)
    {
        $totalPro = 0;
        $minPro = $packageInfo[0]['pro'];
        foreach ($packageInfo as $item) {
            if (!isset($item['packageId'])) {
                return false;
            }
            if (!isset($item['pro'])) {
                return false;
            }
            $totalPro += $item['pro'];
            $minPro = $minPro > $item['pro'] ? $item['pro'] : $minPro;
        }

        if ($totalPro != 100) {
            return false;
        }
        $totalPro = 0;
        $minPro = strtolower((string) $minPro);
        if (strpos($minPro, 'e') !== false) {
            //小数被转成了科学计数法，需要特殊处理
            $tmp = explode('e', $minPro);
            $floatNum = abs($tmp[1]) + strlen($tmp[0]) - 2;
        } else {
            $floatNum = strpos($minPro, '.') !== false ? strlen(explode('.', $minPro)[1]) : 0;
        }
        $num = pow(10, $floatNum);

        $newPackageInfo = [];
        foreach ($packageInfo as $index => $item) {
            $item['pro'] *= $num; //将所有概率转成整数
            $item['pro'] *= $enlarge; //将概率放大指定倍数，放大的目的是让实验次数多，概率更准确
            $totalPro += $item['pro'];
            $newPackageInfo[$index] = $item;
        }
        foreach ($newPackageInfo as $index => $item) {
            $randNum = mt_rand(1, $totalPro);
            if ($randNum <= $item['pro']) {
                return $packageInfo[$index];
            } else {
                $totalPro -= $item['pro'];
            }
        }
    }

    /**
     * 生成ODP调用完整的header信息
     * @param array $headerInfo odp请求header中的部分信息
     * $headerInfo = [
     *      'destination' => '【必须传】Request-header中的X-Odp-Destination-Service的值',
     *      'secret' => '【必须传】用于生成Request-header中的X-Odp-Signature签名用的secret，咨询接口提供方获取该值'
     *      'source' => '【选填】Request-header中的X-Odp-Source-Service的值，如果不传，内部活动：取ams请求中的sServiceType参数的值，Ulink活动：取game值'
     * ]
     * @return array odp的header头部信息数组，包含以下头信息
     * [
     *      'Content-Type: ' => '',
     *      'X-Odp-Destination-Service: ' => '',
     *      'X-Odp-Source-Service: ' => '',
     *      'X-Odp-Timestamp: ' => '',
     *      'X-Odp-Nonce: ' => '',
     *      'X-Odp-Signature: ' => '',
     * ]
     */
    public static function getOdpHeader(array $headerInfo)
    {
        $timestamp = time();
        $nonce = Common::getRandStr(6, 3, 1);
        $destination = $headerInfo['destination'];
        if (RUN_ENV == 'internal') {
            $source = isset($headerInfo['source']) ? $headerInfo['source'] : Request::param('sServiceType');
        } else {
            $source = isset($headerInfo['source']) ? $headerInfo['source'] : Request::param('game');
        }

        recordLog('odp接口请求参数信息：headerInfo=' . var_export($headerInfo, true), __FILE__, __LINE__);

        //内部活动并且非swoole环境的header，跟PHP7的curl要求一致
        if (RUN_ENV == 'internal' && !isSwoole()) {
            $header = [
                'Content-Type: application/json',
                'X-Odp-Destination-Service: ' . $destination,
                'X-Odp-Source-Service: ' . $source,
                'X-Odp-Timestamp: ' . $timestamp,
                'X-Odp-Nonce: ' . $nonce,
                'X-Odp-Signature: ' . md5(implode(',', [$destination, $source, $timestamp, $nonce, $headerInfo['secret']])),
            ];
        } else {
            $header = [
                'Content-Type' => 'application/json',
                'X-Odp-Destination-Service' => $destination,
                'X-Odp-Source-Service' => $source,
                'X-Odp-Timestamp' => $timestamp,
                'X-Odp-Nonce' => $nonce,
                'X-Odp-Signature' => md5(implode(',', [$destination, $source, $timestamp, $nonce, $headerInfo['secret']])),
            ];
        }

        return $header;
    }
}
